#include <QImage>
#include <QPainter>
#include <QThreadPool>
#include <QDir>
#include <QSqlQuery>
#include <QSqlError>

#include <provider/tmsprovider.h>
#include <utils/network.h>

namespace EasyGIS
{
  TileDownloadTask::TileDownloadTask(TileInfo tile, QObject* parent)
    : QObject(parent),
      mTile(std::move(tile))
  {
  }

  void
  TileDownloadTask::run()
  {
    Network web{};
    QByteArray data = web.httpsRequest(mTile.url);
    mTile.data = data;
    tileReady(mTile);
  }

  TmsProvider::TmsProvider(QObject* parent)
    : LayerProvider(parent),
      mImage(nullptr)
  {
    if (!QFile::exists(mDbName)) {
      QFile dbFile{mDbName};
      dbFile.open(QIODevice::ReadWrite);
      dbFile.close();
    }
  }

  TmsProvider::~TmsProvider()
  {
    delete mImage;
  }

  bool
  TmsProvider::initCache()
  {
    mDbConn = QSqlDatabase::addDatabase("QSQLITE", id());
    mDbConn.setDatabaseName(mDbName);
    if (!mDbConn.open()) {
      qCritical() << "缓存数据库打开失败";
      return false;
    }

    QSqlQuery sql{mDbConn};
    if (!mDbConn.tables().contains(mTableName)) {
      sql.prepare(QString("create table %1 ("
        "id integer primary key autoincrement,"
        "provider text not null,"
        "zoom integer not null,"
        "position text not null,"
        "data blob not null"
        ")").arg(mTableName));
      if (!sql.exec()) {
        qCritical() << "缓存表创建失败" << sql.lastError().text();
        return false;
      }
    }

    mDbConn.close();
    return true;
  }

  QByteArray
  TmsProvider::getCache(const QPoint& pos, int zoom)
  {
    QByteArray res{};
    mDbConn.open();
    QSqlQuery select{mDbConn};
    select.prepare(QString("select data from %1 "
      "where provider = :provider "
      "and zoom = :zoom "
      "and position = :position "
      "order by id desc limit 1"
    ).arg(mTableName));
    select.bindValue(":provider", id());
    select.bindValue(":zoom", zoom);
    select.bindValue(":position", QString("%1:%2").arg(pos.x()).arg(pos.y()));
    if (!select.exec()) {
      qDebug() << pos << "查询失败=>" << select.lastError().text();
      mDbConn.close();
      return res;
    }
    if (!select.seek(0)) {
      mDbConn.close();
      return res;
    }

    res = select.value(0).toByteArray();
    mDbConn.close();
    return res;
  }

  bool
  TmsProvider::addCache(const QPoint& pos, int zoom, QByteArray data)
  {
    if (data.isEmpty()) {
      qWarning() << "瓦片数据为空";
      return false;
    }
    mDbConn.open();
    QSqlQuery sql{mDbConn};
    sql.prepare(QString{
      "insert into %1 (provider, zoom, position, data)"
      "values( :provider, :zoom, :position, :data)"
    }.arg(mTableName));
    sql.bindValue(":tbname", mTableName);
    sql.bindValue(":provider", id());
    sql.bindValue(":zoom", zoom);
    sql.bindValue(":position", QString("%1:%2").arg(pos.x()).arg(pos.y()));
    sql.bindValue(":data", data);

    if (!sql.exec()) {
      qCritical() << pos << "=>" << sql.lastError().text();
      mDbConn.close();
      return false;
    }

    mDbConn.close();
    return true;
  }

  bool
  TmsProvider::cacheContains(const QPoint& pos, int zoom)
  {
    QByteArray res = getCache(pos, zoom);
    return !res.isEmpty();
  }

  void
  TmsProvider::createTask(const QRectF& rect, int zoom)
  {
    newImage(rect);

    /// 创建下载瓦片任务
    const QSize sz = tileSize();
    int xMin = qFloor(rect.topLeft().x() / sz.width());
    double xOffset = rect.topLeft().x() / sz.width() - xMin;
    int xMax = qFloor(rect.bottomRight().x() / sz.width());
    int yMin = qFloor(rect.topLeft().y() / sz.height());
    double yOffset = rect.topLeft().y() / sz.height() - yMin;
    int yMax = qFloor(rect.bottomRight().y() / sz.height());
    for (int i = xMin; i <= xMax; ++i) {
      for (int j = yMin; j <= yMax; ++j) {
        TileInfo tile{};
        tile.index = QPointF{i - xMin - xOffset, j - yMin - yOffset};
        tile.position = QPoint{i, j};
        tile.zoom = zoom;
        tile.coord = QPoint{i - xMin, j - yMin};
        tile.url = tileUrl(PointXY{static_cast<double>(i), static_cast<double>(j)}, zoom);
        auto tileData = getCache(tile.position, tile.zoom);
        if (!tileData.isEmpty()) {
          tile.data = tileData;
          tileReady(tile);
          continue;
        }

        auto* task = new TileDownloadTask(tile);
        QObject::connect(task, &TileDownloadTask::tileReady, this, &TmsProvider::tileReady);
        QThreadPool::globalInstance()->start(task);
      }
    }
  }

  void
  TmsProvider::tileReady(TileInfo tile)
  {
    if (!cacheContains(tile.position, tile.zoom)) {
      addCache(tile.position, tile.zoom, tile.data);
    }
    QPainter painter{mImage};
    QImage img = QImage::fromData(tile.data);
    if (img.isNull()) {
      return;
    }

    double xPos = tile.index.x() * tileSize().width();
    double yPos = tile.index.y() * tileSize().height();

    painter.drawImage(QPointF(xPos, yPos), img);
  }

  void
  TmsProvider::newImage(const QRectF& rect)
  {
    QSize imgSize{int(rect.width()), int(rect.height())};
    if (!mImage || imgSize != mImage->size()) {
      mImage = new QImage(imgSize, QImage::Format_RGB32);
    }
    mImage->fill(Qt::white);
  }
}
